using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using MacomberMap.Common.Components;
using MacomberMap.Common.Database_Interface.CIM.Components;
using MacomberMap.Common.Database_Interface.CIM.CIM_Interface;
using System.Data;
using MacomberMap.Common.Logging;
using System.Drawing;
using MacomberMap.Common.Types;
using MacomberMap.Common.Internals;
using MacomberMap.Common.User_Interfaces.Components;
using MacomberMap.Common.Events;
using System.Security.Cryptography;

namespace MacomberMap.Common.Database_Interface.CIM
{
    /// <summary>
    /// This class is responsible for handling the interface between a CIM database and the appropriate Macomber Map structures
    /// </summary>
    public class MM_Database_CIM : MM_Database
    {
        #region Variable declarations
        ///<summary>The list of types, and mapping to their alloctions</summary>
        public static MM_Indexed_Dictionary<String, int> Types;
        
        ///<summary>The list of attributes, and mapping to their allocations</summary>
        public static MM_Indexed_Dictionary<String, int> Attributes;

        ///<summary>The collection of all elements by type</summary>
        public static Dictionary<Byte, List<CIM_Element>> ElementsByType = new Dictionary<byte, List<CIM_Element>>(255);

        /// <summary>The largest available mRID value</summary>
        public static Int32 MaxMRID = 0;

        /// <summary>
        /// Retrieve all elements of a specified type
        /// </summary>
        /// <param name="ElementType">The type of element to locate</param>
        /// <returns></returns>
        public static List<CIM_Element> ElementsOfType(String ElementType)
        {
            int ElemType;
            List<CIM_Element> OutList;
            if (Types.TryGetValue(ElementType, out ElemType))
                if (ElementsByType.TryGetValue((byte)ElemType, out OutList))
                    return OutList;
            return new List<CIM_Element>(0);
                        
        }


        ///<summary>The collection of all elements by type</summary>
        public static MM_Indexed_Dictionary<MM_rdfID, CIM_Element> Elements;

        /// <summary>The collection of all attributes by type</summary>
        public static Dictionary<Byte, List<UInt16>> AttributesByType;

        /// <summary>The collection of all types by attribute</summary>
        public static Dictionary<ushort, List<byte>> TypesByAttribute;

        /// <summary>The path to the CIM XML file.</summary>
        public static string CIMFileName;

        /// <summary>Our temporary element collection to speed up creation of new elements</summary>
        public static Stack<CIM_Element> TempElements;

        /// <summary>The loader tasked with opening the CIM file</summary>
        public static CIM_File_Interface CIMLoader;

        /// <summary>The collection of CIM elements by TEID</summary>
        public static Dictionary<UInt64, CIM_Element> TEIDs = new Dictionary<ulong, CIM_Element>();

        /// <summary>The collection of CIM elements by TEID</summary>
        public static Dictionary<CIM_Element, UInt64> ReverseTEIDs = new Dictionary<CIM_Element, UInt64>();

        /// <summary>Our collection of TEIDs that have been successfully matched</summary>
        public static Dictionary<UInt64, bool> TEIDMatches = new Dictionary<ulong, bool>();

        /// <summary>Our collection of type to types</summary>
        public static Dictionary<Byte, List<byte>> TypeToTypes = new Dictionary<byte, List<byte>>();
        
        /// <summary>Our collection of type from types</summary>
        public static Dictionary<Byte, List<byte>> TypeFromTypes = new Dictionary<byte, List<byte>>();
        #endregion


        #region Initialization
        /// <summary>
        /// Initialize a connection to the CIM database
        /// </summary>
        /// <param name="CIMFileName"></param>
        public MM_Database_CIM(String CIMFileName)
            : base("CIM file " + Path.GetFileNameWithoutExtension(CIMFileName))
        {
            //Initialize our incoming structures            
            Elements = new MM_Indexed_Dictionary<MM_rdfID, CIM_Element>(MM_Configuration.InitialHashSize);
            ElementsByType = new Dictionary<byte, List<CIM_Element>>(255);
            Types = new MM_Indexed_Dictionary<string, int>(255);
            AttributesByType = new Dictionary<byte, List<ushort>>(255);
            TypesByAttribute = new Dictionary<ushort, List<byte>>(255);
            Attributes = new MM_Indexed_Dictionary<string, int>(255);
            TypeToTypes = new Dictionary<byte, List<byte>>(255);
            TypeFromTypes = new Dictionary<byte, List<byte>>(255);
            TempElements = new Stack<CIM_Element>(MM_Configuration.InitialHashSize);
            for (int a = 0; a < MM_Configuration.InitialHashSize; a++)
                TempElements.Push(new CIM_Element());
            MM_Database_CIM.CIMFileName = CIMFileName;
            MM_Database_CIM.LoadCIM(CIMFileName);

        }
        #endregion

        #region CIM Loading
        /// <summary>
        /// Load a new CIM file
        /// </summary>
        /// <param name="FileName">The file name to be openend</param>        
        public static void LoadCIM(String FileName)
        {
            CIMFileName = Path.GetDirectoryName(FileName) + "\\" + Path.GetFileNameWithoutExtension(FileName) + ".xml";
            if (Path.GetExtension(FileName).ToLower() == ".cimindex" || Path.GetExtension(FileName).ToLower() == ".idx")
                CIMLoader = new CIM_Index_Reader(FileName);
            else
                CIMLoader = new CIM_File_Reader(FileName, 1024 * 1024 * 512);
        }


        /// <summary>
        /// Add a CIM element to the repository
        /// </summary>
        /// <param name="rdfID">The rdf:ID of the element</param>
        /// <param name="ElemStart">The starting position of the element</param>
        /// <param name="ElemLength">The length of the element</param>
        /// <param name="ElementToProcess">The XML of the element to be processed</param>
        /// <param name="ElementType">The type of element to be processed</param>
        /// <returns>The newly-created CIM element</returns>
        public static CIM_Element AddCIMElement(string rdfID, long ElemStart, int ElemLength, String ElementType, string ElementToProcess)
        {
            //First, convert our rdf:ID to our optimized-for-storage ID.
            MM_rdfID CIMrdfID = new MM_rdfID(rdfID);
            if (CIMrdfID.ToString() != rdfID)
                MM_Log.LogEvent(new MM_Event_IDMismatch(rdfID, CIMrdfID.ToString()));


            //Locate our element, and set its parameters
            CIM_Element NewElement = LocateElement(CIMrdfID);
            NewElement.Start = ElemStart;
            NewElement.Length = ElemLength;
            NewElement.rdfID = CIMrdfID;
            NewElement.Type = ElementType;
            
            
            //Now, manage our attributes and links
            if (!String.IsNullOrEmpty(ElementToProcess))
            {
                ManageAttributesAndLinks(NewElement, ElementToProcess);
                NewElement.CheckSum = BuildSortedChecksum(ElementToProcess);

            }


            return NewElement;
        }

        /// <summary>
        /// Build a sorted checksum of our element
        /// </summary>
        /// <param name="InXml"></param>
        private static byte[] BuildSortedChecksum(String InnerXml)
        {
            //First, build our dictionary of elements
            List<KeyValuePair<String, String>> InValues = new List<KeyValuePair<string, string>>();
            if (InnerXml.Length > 0)
                InValues.Add(new KeyValuePair<String,String>(" Type", InnerXml.Substring(1, InnerXml.IndexOf(' ') - 1)));
            else
                InValues.Add(new KeyValuePair<String,String>(" Type", "UNKNOWN"));
            int CurPos = 1;
            if (InnerXml.Length > 0)
                while ((CurPos = InnerXml.IndexOf('<', CurPos) + 1) != 0)
                {
                    String AttrName = InnerXml.Substring(CurPos, InnerXml.IndexOf('>', CurPos) - CurPos);
                    String Val = null;
                    if (AttrName.Contains(" "))
                    {
                        Val = AttrName.Substring(AttrName.IndexOf('=') + 2);
                        Val = Val.Substring(0, Val.Length - 2);
                        AttrName = AttrName.Substring(0, AttrName.IndexOf(' '));
                    }
                    else if (AttrName[0] != '/')
                    {
                        Val = InnerXml.Substring(InnerXml.IndexOf('>', CurPos) + 1);
                        Val = Val.Substring(0, Val.IndexOf('<'));
                    }
                    InValues.Add(new KeyValuePair<String,String>(AttrName, Val));
                }
            InValues.Sort(new KvpComparer());
            StringBuilder sB = new StringBuilder();
            foreach (KeyValuePair<String, String> kvp in InValues)
                sB.AppendLine(kvp.Key + "=" + kvp.Value);
            return SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(sB.ToString()));
        }

        /// <summary>
        /// This class provides a comparison point for our elements
        /// </summary>
        private class KvpComparer : IComparer<KeyValuePair<String, String>>
        {
            /// <summary>
            /// Compare two points
            /// </summary>
            /// <param name="x"></param>
            /// <param name="y"></param>
            /// <returns></returns>
            public int Compare(KeyValuePair<string, string> x, KeyValuePair<string, string> y)
            {
                int CompareTitle = string.Compare(x.Key, y.Key, false);
                if (CompareTitle != 0)
                    return CompareTitle;
                else
                    return String.Compare(x.Value, y.Value, false);
            }
        }
        #endregion

        #region CIM <-> MM matching
        /// <summary>
        /// Locate a KV Level for a particular CIM element
        /// </summary>
        /// <param name="Elem">The voltage level to check</param>
        /// <returns></returns>
        public static MM_KVLevel LocateKVLevel(CIM_Element Elem)
        {
            if (Elem == null)
                return null; //MM_Overall_Display.KVLevels["Other KV"];
            else if (MM_Overall_Display.KVLevels.ContainsKey(Elem.Name))
                return MM_Overall_Display.KVLevels[Elem.Name];
            else if (MM_Overall_Display.KVLevels.ContainsKey(Elem.Name + " KV"))
                return MM_Overall_Display.KVLevels[Elem.Name + " KV"];
            else
                return MM_Overall_Display.KVLevels["Other KV"];
        }

        /// <summary>
        /// Determine the MM element type for a CIM element
        /// </summary>
        /// <param name="Elem"></param>
        /// <returns></returns>
        public static MM_Element_Type LocateElementType(CIM_Element Elem)
        {
            foreach (MM_Element_Type ElemType in MM_Overall_Display.ElemTypes.Values)
                if (ValidateType(ElemType, Elem))
                    return ElemType;
            return null;
        }
        #endregion

        #region CIM Element parsing
        /// <summary>
        /// Locate a CIM element by its rdf:ID. If one cannot be found, create a stub
        /// </summary>
        /// <param name="rdfID">The rdf:ID to search for.</param>
        /// <returns></returns>
        public static CIM_Element LocateElement(MM_rdfID rdfID)
        {
            CIM_Element OutElement;

            //If the element isn't already in the database, pull it in
            if (!Elements.TryGetValue(rdfID, out OutElement))
            {
                while ((OutElement = GetTempElement()) == null)
                    System.Threading.Thread.Sleep(0);
                OutElement.rdfID = rdfID;
                OutElement.Type = "UNKNOWN";
                OutElement.Start = -1;
                OutElement.Length = -1;

                //Add our element to the collections
                Elements.Add(rdfID, OutElement);

            }
            return OutElement;
        }

        /// <summary>
        /// If a substation, retrieve a list of elements. If any other element, locate a trace
        /// </summary>
        /// <param name="BaseElement">The elements to be loaded</param>
        /// <returns></returns>
        public override List<MM_Element> LoadElements(MM_Element BaseElement)
        {
            List<MM_Element> OutElements = new List<MM_Element>();
            CIM_Element BaseCIM = TEIDs[BaseElement.TEID];
            if (BaseCIM.Type == "cim:Substation")
            {
                foreach (CIM_Element Elem in BaseCIM.Elements)
                    if (Elem.TEID != "")
                        OutElements.Add(LocateElement(Convert.ToUInt64(Elem.TEID)));
            }
            else
            {
                foreach (CIM_Element Elem in CIM_Tracer.Tracer(BaseCIM, true,true, null).Keys)
                    if (Elem.TEID != "")
                        OutElements.Add(LocateElement(Convert.ToUInt64(Elem.TEID)));
            }

            while (OutElements.Remove(null)) { }
            OutElements.TrimExcess();
            return OutElements;
        }


        /// <summary>
        /// Locate an element by TEID.
        /// </summary>
        /// <param name="TEID"></param>
        /// <returns></returns>
        public override MM_Element LocateElement(ulong TEID)
        {
            return LocateElementByTEID(TEID, MM_Repository.TEIDs);
        }

        /// <summary>
        /// Locate an element by TEID
        /// </summary>
        /// <param name="TEID"></param>
        /// <param name="ExistingElements">The collectiof existing elements</param>
        /// <returns></returns>
        public static MM_Element LocateElementByTEID(ulong TEID, Dictionary<UInt64, MM_Element> ExistingElements)
        {
            if (MM_Repository.TEIDs.ContainsKey(TEID))
                return MM_Repository.TEIDs[TEID];
            else if (MM_Database_CIM.TEIDs.ContainsKey(TEID))
                return MM_Database_CIM.LocateElement(MM_Database_CIM.TEIDs[TEID], ExistingElements);
            else
                return null;
        }

        /// <summary>
        /// Determine the next available TEID
        /// </summary>
        private static UInt64 NextTEID()
        {
            UInt64 OutTEID = (UInt64)TEIDs.Count + 1;
            while (TEIDs.ContainsKey(OutTEID))
                OutTEID++;
            return OutTEID;
        }


        /// <summary>
        /// Locate/create an MM element based on a CIM element
        /// </summary>
        /// <param name="Elem">The CIM element</param>
        /// <param name="ExistingElements">Our collectino of existing elements</param>
        /// <returns></returns>
        public static MM_Element LocateElement(CIM_Element Elem, Dictionary<UInt64, MM_Element> ExistingElements)
        {
            CIM_Element InElem = Elem;

            if (Elem == null)
                return null;
            else if (Elem.Type == "cim:ACLineSegment")
                InElem = Elem.Parse("cim:Line");
            else if (Elem.Type == "cim:SeriesCompensator")
                InElem = Elem.Parse("cim:Line");
            else if (Elem.Type == "cim:Line")
                Console.WriteLine("Whoops!");
            
            
            UInt64 TEID = 0;
            MM_Element OutElement = null;
            ReverseTEIDs.TryGetValue(Elem, out TEID);
            if (TEID != 0)
                if (MM_Repository.TEIDs.TryGetValue(TEID, out OutElement))
                    return OutElement;
                else if (ExistingElements.TryGetValue(TEID, out OutElement))
                    return OutElement;                        


            //First, create our new element
            
            MM_Element_Type ElemType = LocateElementType(InElem);
            if (ElemType == null)
                return null;
            else if (ElemType.Name == "Node" || ElemType.Name == "ConnectivityNode")
                OutElement = new MM_Node();
            else if (ElemType.Name == "ElectricalBus")
                OutElement = new MM_ElectricalBus();
            else if (ElemType.Name == "Breaker")
                OutElement = new MM_Breaker();
            else if (ElemType.Name == "DCTie")
                OutElement = new MM_DCTie();
            else if (ElemType.Name == "LoadZone" || ElemType.Name == "WeatherZone")
            {
                if (MM_Repository.Zones.ContainsKey(Elem.Name))
                    return MM_Repository.Zones[Elem.Name];
                OutElement = new MM_Zone(MM_Repository.TitleCase(Elem.Name), 0, ElemType);
                MM_Repository.Zones.Add(MM_Repository.TitleCase(Elem.Name), OutElement as MM_Zone);
            }
            else if (ElemType.Name == "Transformer")
            {
                MM_Transformer OutTransformer = new MM_Transformer();
                OutElement = OutTransformer;
                List<MM_TransformerWinding> OutWindings = new List<MM_TransformerWinding>(2);
                foreach (CIM_Element OutWinding in Elem.TransformerWinding)
                {
                    MM_TransformerWinding Winding = new MM_TransformerWinding();
                    Winding.Transformer = OutTransformer;
                    Winding.TEID = Convert.ToUInt64(OutWinding.TEID);
                    Winding.KVLevel = LocateKVLevel(OutWinding.VoltageLevel);
                    OutWindings.Add(Winding);
                }
                OutTransformer.Windings = OutWindings.ToArray();
                OutTransformer.PhaseShifter = Elem["cim:PowerTransformer.transformerType"].IndexOf("Phase") != -1;
            }
            else if (ElemType.Name == "TransformerWinding")
                OutElement = new MM_TransformerWinding();
            else if (ElemType.Name == "PricingVector")
                OutElement = new MM_PricingVector();
            else if (ElemType.Name == "EPSMeter")
                OutElement = new MM_EPSMeter();
            else if (ElemType.Name == "Substation")
            {
                if (MM_Repository.Substations.ContainsKey(MM_Repository.TitleCase(Elem.Name)))
                    OutElement = MM_Repository.Substations[MM_Repository.TitleCase(Elem.Name)];
                else
                {
                    OutElement = new MM_Substation();
                    OutElement.Name = MM_Repository.TitleCase(Elem.Name);
                    MM_Repository.Substations.Add(MM_Repository.TitleCase(Elem.Name), OutElement as MM_Substation);
                }
            }
            else if (ElemType.Name == "Contingency")
                OutElement = new MM_Contingency(Elem.Name, Elem["cim:IdentifiedObject.Description"]);
            else if (ElemType.Name == "Unit")
                OutElement = new MM_Unit();
            else if (ElemType.Name == "ShuntCompensator")
                OutElement = new MM_ShuntCompensator();
            else if (ElemType.Name == "Line")
                OutElement = new MM_Line();
            else if (ElemType.Name == "Company")
            {
                OutElement = new MM_Company();
                (OutElement as MM_Company).DUNS = Elem["etx:Company.DUNS"];
                (OutElement as MM_Company).PrimaryPhone = Elem["etx:Company.PrimaryPhone"];
            }
            else if (ElemType.Name == "BusbarSection")
                OutElement = new MM_BusbarSection();
            else
                OutElement = new MM_Element();
            OutElement.Name = MM_Repository.TitleCase(Elem.Name);
            OutElement.ElemType = ElemType;


            //If we have no element, create a new generic one
            if (OutElement == null)
            {
                OutElement = new MM_Element();
                OutElement.ElemType = ElemType;
            }

            //Now that we have our element created, start assigning its parameters
            OutElement.Name = MM_Repository.TitleCase(Elem.Name);
            OutElement.TEID = TEID;            

            if (OutElement.TEID != 0)
                ExistingElements.Add(OutElement.TEID, OutElement);

            OutElement.KVLevel = LocateKVLevel(Elem.VoltageLevel);
            if (Elem.Owner != null)
                OutElement.Owner = LocateElement(Elem.Owner, ExistingElements) as MM_Company;
            if (Elem.Operator != null)
                OutElement.Operator = LocateElement(Elem.Operator, ExistingElements) as MM_Company;
            if (OutElement is MM_Substation == false && Elem.Substation != null)
                OutElement.Substation = LocateElement(Elem.Substation, ExistingElements) as MM_Substation;
            if (OutElement is MM_Node)
            {
                MM_Node OutNode = OutElement as MM_Node;
                List<MM_Element> OutLinks = new List<MM_Element>();
                MM_Element LinkedMMElem;
                foreach (CIM_Element LinkedElem in Elem.Elements)
                    if (LinkedElem != Elem && (LinkedMMElem = LocateElement(LinkedElem, ExistingElements)) != null)
                        if (LinkedMMElem is MM_BusbarSection)
                            OutNode.BusbarSection = LinkedMMElem as MM_BusbarSection;
                        else
                            OutLinks.Add(LinkedMMElem);
                OutNode.ConnectedElements = OutLinks.ToArray();
                OutNode.ElectricalBus = LocateElement(Elem.Parse("etx:ElectricalBus"), ExistingElements) as MM_ElectricalBus;
            }
            else if (OutElement is MM_ElectricalBus)
            {
                if (OutElement.TEID == 0)
                {
                    OutElement.TEID = NextTEID();
                    TEIDs.Add(OutElement.TEID, Elem);
                    ReverseTEIDs.Add(Elem, OutElement.TEID);
                }
                (OutElement as MM_ElectricalBus).AssociatedNode = MM_Database_CIM.LocateElement(Elem.Parse("cim:ConnectivityNode"), ExistingElements) as MM_Node;
            }
            else if (OutElement is MM_BusbarSection)
                (OutElement as MM_BusbarSection).AssociatedNode = LocateElement(Elem.Parse("cim:Terminal>cim:ConnectivityNode"), ExistingElements) as MM_Node;
            else if (OutElement is MM_EPSMeter)
                (OutElement as MM_EPSMeter).RID = MM_Repository.TitleCase(Elem["etx:EPSMeter.RID"]);
            else if (OutElement is MM_PricingVector)
            {
                MM_PricingVector OutPrice = OutElement as MM_PricingVector;
                OutPrice.EPSMeter = MM_Database_CIM.LocateElement(Elem.Parse("etx:EPSMeter"), ExistingElements) as MM_EPSMeter;

                CIM_Element FoundNode = Elem.Parse("cim:Terminal>cim:ConnectivityNode");
                MM_Database_CIM.LocateElement(FoundNode, ExistingElements);
                OutPrice.NodeElement = MM_Database_CIM.LocateElement(FoundNode, ExistingElements) as MM_Node;

                foreach (CIM_Element TerminalElem in Elem.Parse("cim:Terminal").Links)
                    if ((TerminalElem.Type == "cim:ACLineSegment" || TerminalElem.Type == "cim:SeriesCompensator") && TerminalElem != Elem)
                        OutPrice.OtherElement = MM_Database_CIM.LocateElement(TerminalElem, ExistingElements);
                    else if (TerminalElem != Elem && TerminalElem != FoundNode && LocateElementType(TerminalElem) != null)
                        OutPrice.OtherElement = MM_Database_CIM.LocateElement(TerminalElem, ExistingElements);
                OutPrice.IsPositive = Convert.ToBoolean(Elem["etx:PricingVector.positiveFlowIn"]);
            }
            else if (OutElement is MM_DCTie)
            {
                MM_DCTie OutTie = OutElement as MM_DCTie;
                OutTie.Name = (OutTie as MM_Element).Name;
                CIM_Element Unit = Elem.Parse("Unit>SynchronousMachine");
                CIM_Element Load = Elem.Parse("Load");

                OutTie.Unit = LocateElement(Unit, ExistingElements) as MM_Unit;
                OutTie.Load = LocateElement(Load, ExistingElements);
                //Locate our associated line by looking at elements around the connectivity node near our element
                foreach (CIM_Element UnitNode in Unit.Parse("cim:Terminal>cim:ConnectivityNode>cim:Terminal>cim:Breaker").Nodes)
                    foreach (CIM_Element LoadNode in Load.Parse("cim:Terminal>cim:ConnectivityNode>cim:Terminal>cim:Breaker").Nodes)
                        if (UnitNode == LoadNode)
                            foreach (CIM_Element LineElem in UnitNode.Elements)
                                if (LineElem.Type == "cim:ACLineSegment")
                                    OutTie.AssociatedLine = LocateElement(LineElem, ExistingElements) as MM_Line;

                if (OutTie.AssociatedLine == null)
                {
                    int CountMatches = 0;
                    foreach (CIM_Element LineElem in Unit.Substation.Elements)
                        if (LineElem.Type == "cim:ACLineSegment")
                            CountMatches++;

                    if (CountMatches == 1)
                        foreach (CIM_Element LineElem in Unit.Substation.Elements)
                            if (LineElem.Type == "cim:ACLineSegment")
                                OutTie.AssociatedLine = LocateElement(LineElem, ExistingElements) as MM_Line;
                }

                OutElement.KVLevel = OutTie.Unit.KVLevel;
                OutElement.Owner = OutTie.Unit.Owner;
                OutElement.Operator = OutTie.Unit.Operator;

            }
            else if (OutElement is MM_Unit)
            {
                MM_Unit OutUnit = OutElement as MM_Unit;
                OutUnit.IsRC = Elem.Type == "cim:CombinedCyclePlant";
                OutUnit.UnitType = Elem.Parse("Unit").Type.Split(':')[1];
            }
            else if (OutElement is MM_Line)
            {
                (OutElement as MM_Line).IsSeriesCompensator = (Elem.Type != "cim:ACLineSegment");
                (OutElement as MM_Line).LineTEID = Convert.ToUInt64(InElem.TEID);
                OutElement.Owner = LocateElement(InElem.Owner, ExistingElements) as MM_Company;
                OutElement.Operator = LocateElement(InElem.Operator, ExistingElements) as MM_Company;
                (OutElement as MM_Line).Substation1 = LocateElement(Elem.ACSegmentStations[0], ExistingElements) as MM_Substation;
                (OutElement as MM_Line).Substation2 = LocateElement(Elem.ACSegmentStations[1], ExistingElements) as MM_Substation;
                if (InElem.InLinks.Length > 1)
                {
                    OutElement.Name = MM_Repository.TitleCase(InElem.Name + Elem.Name);
                    (OutElement as MM_Line).IsMultipleSegment = true;
                }
                else
                    OutElement.Name = MM_Repository.TitleCase(InElem.Name);

            }

            else if (OutElement is MM_Substation)
            {
                MM_Substation OutSub = OutElement as MM_Substation;
                if (String.IsNullOrEmpty(OutSub.LongName = MM_Repository.TitleCase(Elem["cim:IdentifiedObject.description"])))
                    OutSub.LongName = OutSub.Name;


                //Try and locate the lat/long if possible
                if (float.IsNaN((OutElement as MM_Substation).LatLong.X))
                    foreach (MM_Database db in MM_Repository.Databases)
                        if (db is MM_Database_Oracle)
                            (db as MM_Database_Oracle).LoadSubstationLatLong(OutElement as MM_Substation);

                //Pull in the list of element types and KV levels                               
                foreach (CIM_Element SubElem in Elem.Elements)
                {
                    //Determine the sub-element's KV level and type
                    MM_KVLevel SubElemKV = LocateKVLevel(SubElem.VoltageLevel);
                    if (SubElemKV != null && !OutSub.KVLevels.Contains(SubElemKV))
                        OutSub.KVLevels.Add(SubElemKV);

                    MM_Element_Type SubElemType = LocateElementType(SubElem);
                    if (SubElemType != null && !OutSub.ElemTypes.Contains(SubElemType))
                        OutSub.ElemTypes.Add(SubElemType);
                }
                OutSub.LoadZone = LocateElement(Elem.Parse("SettlementLoadZone"), ExistingElements) as MM_Zone;
                OutSub.WeatherZone = LocateElement(Elem.Parse("SubGeographicalRegion"), ExistingElements) as MM_Zone;
            }
            else if (OutElement is MM_TransformerWinding)
                (OutElement as MM_TransformerWinding).Transformer = LocateElement(Elem.Parse("PowerTransformer"), ExistingElements) as MM_Transformer;

            return OutElement;
        }

       



        /// <summary>
        /// Locate a CIM element by TEID, and check its type
        /// </summary>
        /// <param name="TEID">The TEID of the element</param>
        /// <param name="ElemType">The element type to search for</param>
        /// <returns></returns>
        public override MM_Element LocateElement(ulong TEID, MM_Element_Type ElemType)
        {
            MM_Element OutElem = LocateElement(TEID);
            if (OutElem != null && OutElem.ElemType != ElemType)
                throw new InvalidDataException(OutElem.ElemType.Name + " " + OutElem.Name + " has TEID " + TEID.ToString("#,##0") + ", although this TEID was requested for type " + ElemType.Name);
            else
                return OutElem;
        }

        /// <summary>
        /// Locate an element by substation and element name
        /// </summary>
        /// <param name="SubstationName">The name of the substation</param>
        /// <param name="Name">The name of the element</param>
        /// <param name="ElemType">The element type</param>
        /// <returns></returns>
        public override MM_Element LocateElement(string SubstationName, string Name, MM_Element_Type ElemType)
        {
            //First, locate our substation
            if (!MM_Repository.Substations.ContainsKey(SubstationName))
                return null;

            //Now parse through all elements, and check whether they match.
            foreach (CIM_Element SubElement in MM_Database_CIM.TEIDs[MM_Repository.Substations[SubstationName].TEID].Elements)
                if (ValidateType(ElemType, SubElement) && SubElement.Name == Name)
                {
                    UInt64 OutTEID = 0;
                    if (MM_Repository.ToUInt64(SubElement.TEID, out OutTEID))
                        return LocateElement(OutTEID);
                    else
                        return null;
                }
            return null;
        }

        /// <summary>
        /// Determine whether a type is a match for the CIM element
        /// </summary>
        /// <param name="ElemType">The element type to search</param>
        /// <param name="SubElement">The CIM element to be searched</param>
        /// <returns></returns>
        public static bool ValidateType(MM_Element_Type ElemType, CIM_Element SubElement)
        {            
            if (String.IsNullOrEmpty(ElemType.CIMName) || !SubElement.Type.EndsWith(ElemType.CIMName, StringComparison.CurrentCultureIgnoreCase))                
                return false;
            else if (String.IsNullOrEmpty(ElemType.CIMParameter))
                return true;
            else
            {
                //First, retrieve the element's XML.
                String ElemXml = SubElement.XML;
                int MathPos = ElemType.CIMParameter.IndexOfAny(new char[] { '<', '>', '=', '!' });
                String AttributeName = ElemType.CIMParameter.Substring(0, MathPos);
                String ResultValue = ElemType.CIMParameter.Substring(MathPos + 1);
                Object AttributeValue;
                if (ResultValue == "null")
                    AttributeValue = null;
                else
                    AttributeValue = float.Parse(ResultValue);

                //First, determine if our attribute is present within the XML.
                Object OutVal = null;
                if (ElemXml.IndexOf(AttributeName + ">",StringComparison.CurrentCultureIgnoreCase) != -1)
                {
                    int XmlPos = ElemXml.IndexOf(AttributeName + ">", StringComparison.CurrentCultureIgnoreCase) + MathPos + 1;
                    OutVal = 0;
                    String CurrentValue = ElemXml.Substring(XmlPos, ElemXml.IndexOf('<', XmlPos) - XmlPos);
                    float floatVal;
                    float.TryParse(CurrentValue, out floatVal);
                    OutVal = floatVal;
                }
                else
                    foreach (CIM_Element Elem in SubElement.Links)
                        if (Elem.Type.Split(':')[1] == AttributeName)
                            OutVal = Elem;

                if (ElemType.CIMParameter[MathPos] == '<')
                    return (float)OutVal < (float)AttributeValue;
                else if (ElemType.CIMParameter[MathPos] == '>')
                    return (float)OutVal > (float)AttributeValue;
                else if (ElemType.CIMParameter[MathPos] == '=')
                {
                    if (AttributeValue is Single)
                        return (float)OutVal == (float)AttributeValue;
                    else if (AttributeValue == null && OutVal == null)
                        return true;                    
                    else
                        return false;
                }
                else if (ElemType.CIMParameter[MathPos] == '!')
                {
                    if (AttributeValue is Single)
                        return (float)OutVal != (float)AttributeValue;
                    else if (AttributeValue == null ^ OutVal == null)
                        return true;
                    else
                        return false;
                }                    
                else
                    return false;
            }
        }


        /// <summary>
        /// Retrieve a temporary element from the stack, and replenish if necessary
        /// </summary>
        /// <returns></returns>
        public static CIM_Element GetTempElement()
        {
            //Check to see whether temporary elements need to be added            
            if (TempElements.Count == 0)
                for (int a = 0; a < MM_Configuration.InitialHashSize / 10; a++)
                    TempElements.Push(new CIM_Element());
            return TempElements.Pop();
        }

        /// <summary>
        /// Go through the XML for an element, and update its links and attribute collections
        /// </summary>
        /// <param name="NewElement">The newly created CIM element</param>
        /// <param name="ElementXML">The XML of our element</param>
        public static void ManageAttributesAndLinks(CIM_Element NewElement, String ElementXML)
        {
            int CurPos = 1;
            //Go through all elements within the XML. If we have data within (not </endelement), add it to our information.
            while ((CurPos = ElementXML.IndexOf("<", CurPos) + 1) != 0)
                if (ElementXML[CurPos] != '/')
                {
                    int EndAttributeName = ElementXML.IndexOfAny(new char[] { '/', '>',' '}, CurPos);
                    String AttributeName = ElementXML.Substring(CurPos, EndAttributeName - CurPos);

                    int QuoteStart = ElementXML.IndexOf('\"', EndAttributeName);
                    String AttributeValue;
                    if (ElementXML[EndAttributeName + 1] == '/' || ElementXML[EndAttributeName + 2] == '/')
                        AttributeValue = "";
                    else if (QuoteStart != -1 && QuoteStart < ElementXML.IndexOfAny(new char[] { '/', '>' }, EndAttributeName))
                        AttributeValue = ElementXML.Substring(QuoteStart + 1, ElementXML.IndexOf('\"', QuoteStart + 1) - QuoteStart - 1);
                    else
                        AttributeValue = ElementXML.Substring(EndAttributeName + 1, ElementXML.IndexOf('<', EndAttributeName) - EndAttributeName - 1);
                    ProcessAttribute(NewElement, AttributeName, AttributeValue);
                }
        }


        /// <summary>
        /// Process an attribute 
        /// </summary>
        /// <param name="InElement">The element being processed</param>
        /// <param name="AttributeName">The name of the attribute</param>
        /// <param name="AttributeValue">The value of the attribute</param>
        public static void ProcessAttribute(CIM_Element InElement, string AttributeName, string AttributeValue)
        {
            //First, check to see whether our attribute is stored
            ushort AttributeNumber = (ushort)Attributes.Lookup(AttributeName);

            //Now, check to see if our type to type to attribute mapping is in place
            lock (AttributesByType)
            {
                if (!AttributesByType.ContainsKey(InElement.TypeByte))
                    AttributesByType.Add(InElement.TypeByte, new List<ushort>(20));
                if (!AttributesByType[InElement.TypeByte].Contains(AttributeNumber))
                    AttributesByType[InElement.TypeByte].Add(AttributeNumber);
            }

            //Also, check to see if our attribute to type mapping is in place
            lock (TypesByAttribute)
            {
                if (!TypesByAttribute.ContainsKey(AttributeNumber))
                    TypesByAttribute.Add(AttributeNumber, new List<byte>(20));
                if (!TypesByAttribute[AttributeNumber].Contains(InElement.TypeByte))
                    TypesByAttribute[AttributeNumber].Add(InElement.TypeByte);
            }

            //Now, check to see if our attribute value is a link, and if so, handle accordingly.
            if (AttributeValue.Length > 0 && AttributeValue[0] == '#')
            {
                CIM_Element Target = LocateElement(new MM_rdfID(AttributeValue.Substring(1)));
                InElement._OutLinks.Add(Target.Index);
                Target._InLinks.Add(InElement.Index);                
            }

            //Also, check to see if the attribute is a TEID. If so, add it in.
            if (AttributeName.EndsWith(".teid", StringComparison.CurrentCultureIgnoreCase) && AttributeValue.Length > 0)
            {
                UInt64 TEID = Convert.ToUInt64(AttributeValue);
                lock (TEIDs)
                    if (TEIDs.ContainsKey(TEID))
                        MM_Log.LogEvent(new MM_Event_DuplicateValue(InElement, TEIDs[TEID], "TEID"));
                    else
                    {
                        TEIDs.Add(TEID, InElement);
                        ReverseTEIDs.Add(InElement, TEID);
                    }
            }

            int CurMRID;
            if (AttributeName.EndsWith(".mrid", StringComparison.CurrentCultureIgnoreCase) && AttributeValue.Length > 0 && Int32.TryParse(AttributeValue, out CurMRID))
                MaxMRID = Math.Max(MaxMRID, CurMRID);
        }
        #endregion

        /// <summary>
        /// Initialize the CIM connector, and open the file into memory
        /// </summary>
        public override void Open()
        {
            String CIMFileName = MM_Database_CIM.CIMFileName;
            MM_Database_CIM.CIMFileName = Path.GetDirectoryName(CIMFileName) + "\\" + Path.GetFileNameWithoutExtension(CIMFileName) + ".xml";
            LoadCIM(MM_Database_CIM.CIMFileName);
        }

        /// <summary>
        /// Close the connection to the CIM file, and clear the memory
        /// </summary>
        public override void Close()
        {
            Elements.Clear();
            TEIDs.Clear();
            ReverseTEIDs.Clear();
            ElementsByType.Clear();
            Types.Clear();
            AttributesByType.Clear();
            TypesByAttribute.Clear();
            Attributes.Clear();
            TempElements.Clear();
            TypeFromTypes.Clear();
            TypeToTypes.Clear();
            GC.Collect();
        }

        /// <summary>
        /// Return the connection state of our CIM file (open/closed)
        /// </summary>
        public override ConnectionState State
        {
            get
            {
                if (Elements.Count > 0)
                    return ConnectionState.Open;
                else
                    return ConnectionState.Closed;
            }
        }

        /// <summary>
        /// Report the type of database this is.
        /// </summary>
        public override string Type
        {
            get { return "CIM"; }
            set
            {
                if (value != "CIM")
                    throw new InvalidDataException("An incorrect database type was passed to the CIM database initializer: " + value + "!");
            }
        }

        /// <summary>
        /// Report the statistics on our file
        /// </summary>
        /// <returns></returns>
        public override DataTable ConnectionStats()
        {
            DataTable OutTable = new DataTable("CIM File stats");
            OutTable.Columns.Add("Variable", typeof(String));
            OutTable.Columns.Add("Value", typeof(String));
            FileInfo fI = new FileInfo(MM_Database_CIM.CIMFileName);
            OutTable.Rows.Add("File size", fI.Length.ToString("#,##0") + " bytes");
            OutTable.Rows.Add("Created on " + fI.CreationTime.ToString());
            OutTable.Rows.Add("Last modified on " + fI.LastWriteTime.ToString());
            OutTable.Rows.Add("Elements", Elements.Count.ToString("#,##0"));
            OutTable.Rows.Add("Attributes", Attributes.Count.ToString("#,##0"));
            OutTable.Rows.Add("TEIDs", TEIDs.Count.ToString("#,##0"));
            OutTable.Rows.Add("Types", Types.Count.ToString("#,##0"));
            foreach (KeyValuePair<byte, List<CIM_Element>> kvp in ElementsByType)
                OutTable.Rows.Add("Type " + Types[(int)kvp.Key], kvp.Value.Count.ToString("#,##0"));
            return OutTable;
        }

        /// <summary>
        /// Load our collection of counties
        /// </summary>
        public override void LoadCounties()
        {

        }

        /// <summary>
        /// Save state coordinates
        /// </summary>
        public override void SaveStateCoordinates()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Save county information
        /// </summary>
        public override void SaveCounties()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Save a boundary
        /// </summary>
        /// <param name="Boundary"></param>
        public override void SaveBoundary(MM_Boundary Boundary)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Load lines from the database
        /// </summary>
        public override void LoadLines()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Merge DC Ties and lines
        /// </summary>
        public override void MergeDCTies()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Load substations in
        /// </summary>
        public override void LoadSubstations()
        {
            throw new Exception("The method or operation is not implemented.");
        }



        /// <summary>
        /// Load KV levels
        /// </summary>
        public override void LoadKVLevels()
        {
            throw new Exception("The method or operation is not implemented.");
        }


        /// <summary>
        /// Save substation coordinates
        /// </summary>
        public override void SaveSubstationCoordinates()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Blank the database
        /// </summary>
        public override void BlankDatabase()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Create tables
        /// </summary>
        /// <param name="DbConnection"></param>
        public override void CreateTables(object DbConnection)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Save county websites
        /// </summary>
        /// <param name="Filename"></param>
        public override void SaveCountyWebsites(string Filename)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Save the configuration parameters for this connector
        /// </summary>
        /// <param name="xW"></param>
        public override void SaveConfigurationParameters(System.Xml.XmlTextWriter xW)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Save a map tile
        /// </summary>
        /// <param name="NewTile"></param>
        public override void SaveMapTile(MM_MapTile NewTile)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Load in a map tile
        /// </summary>
        /// <param name="mapType"></param>
        /// <param name="TileXY"></param>
        /// <param name="ZoomLevel"></param>
        /// <param name="Tile"></param>
        /// <returns></returns>
        public override bool LoadMapTile(MM_MapTile.MapType mapType, System.Drawing.Point TileXY, int ZoomLevel, out System.Drawing.Bitmap Tile)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Look up a user
        /// </summary>
        /// <param name="UserName"></param>
        /// <returns></returns>
        public override int LookupUser(string UserName)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Load in a role
        /// </summary>
        /// <param name="UserRole"></param>
        /// <param name="DefaultView"></param>
        /// <param name="PermissionsXML"></param>
        public override void LoadRole(int UserRole, out int DefaultView, out string PermissionsXML)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Load the collection of weather zones
        /// </summary>
        public override void LoadZones()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Load the user roles and databases
        /// </summary>
        /// <returns></returns>
        public override System.Data.DataSet LoadUsersAndRoles()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Load the list of element types
        /// </summary>
        public override void LoadElementTypes()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Load the list of violation types
        /// </summary>
        public override void LoadEventTypes()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Load in the list of views
        /// </summary>
        /// <param name="UserRole"></param>
        /// <param name="DefaultView"></param>
        public override void LoadViews(int UserRole, int DefaultView)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Load in the list of one-lines
        /// </summary>
        /// <returns></returns>
        public override DataTable LoadOneLines()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Create our list of companies
        /// </summary>
        public override void LoadCompanies()
        {
            foreach (CIM_Element Elem in ElementsByType[(byte)Types["cim:Company"]])
            {
                MM_Company NewCompany = new MM_Company();
                NewCompany.Name = MM_Repository.TitleCase(Elem.Name);
                MM_Repository.ToUInt64(Elem.TEID, out NewCompany.TEID);
                NewCompany.DUNS = Elem["etx:Company.DUNS"];
                NewCompany.PrimaryPhone = Elem["etx:Company.PrimaryPhone"];
                MM_Repository.Companies.Add(NewCompany.Name, NewCompany);
            }
        }

        /// <summary>
        /// Load in a one-line for a particular item
        /// </summary>
        /// <param name="TEID"></param>
        /// <returns></returns>
        public override string LoadOneLine(ulong TEID)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Load in a one line for the specified substation
        /// </summary>
        /// <param name="Name"></param>
        /// <returns></returns>
        public override string LoadOneLine(string Name)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Save the one-line for a particular item
        /// </summary>
        /// <param name="BaseElement"></param>
        /// <param name="OneLineXML"></param>
        public override void SaveOneLine(MM_Element BaseElement, string OneLineXML)
        {
            throw new Exception("The method or operation is not implemented.");
        }

       

        /// <summary>
        /// Locate an element in CIM based on its name, type and location
        /// </summary>
        /// <param name="SubstationName"></param>
        /// <param name="ElemType"></param>
        /// <param name="ElemName"></param>
        /// <param name="VoltageLevel"></param>
        /// <returns></returns>
        public static CIM_Element LocateElement(string SubstationName, string ElemType, string ElemName, string VoltageLevel)
        {            

            //If we have no substation, start by looking at element name and type
            if (String.IsNullOrEmpty(SubstationName))                
            {
                foreach (String CIMType in Types.Keys) 
                    if (CIMType.EndsWith(ElemType, StringComparison.CurrentCultureIgnoreCase))                        
                            foreach (CIM_Element Elem in ElementsOfType(CIMType))
                                if (String.Equals(Elem.Name, ElemName, StringComparison.CurrentCultureIgnoreCase))
                                    return Elem;
                return null;
            }

            //First, locate the substation in question
            CIM_Element SubElement = null;
            if (MM_Repository.Substations.ContainsKey(SubstationName))
                SubElement = TEIDs[MM_Repository.Substations[SubstationName].TEID];
            else
                foreach (MM_Element Elem in MM_Repository.MismatchedTEIDs.Values)
                    if (Elem is MM_Substation && Elem.Name == SubstationName)
                    {
                        SubElement = TEIDs[Elem.TEID];
                        break;
                    }

            //If we have no substation or our target is a substation, return what we have 
            if (SubElement == null)
                foreach (CIM_Element Elem in MM_Database_CIM.ElementsOfType("cim:Substation"))
                    if (String.Compare(Elem.Name, SubstationName, true) == 0)
                    {
                        SubElement = Elem;
                        MM_Substation NewSub = new MM_Substation();
                        NewSub.Name = SubstationName;
                        NewSub.TEID = Convert.ToUInt64(Elem.TEID);
                        MM_Repository.Substations.Add(NewSub.Name, NewSub);
                    }


            //If not, search through all of that substation's items, in order to find the appropriate one
            if (ElemType == "Substation")
                return SubElement;
            else if (ElemType == "ConnectivityNode")
            {
                foreach (CIM_Element Node in SubElement.Nodes)
                    if (String.Equals(Node.Name, ElemName, StringComparison.CurrentCultureIgnoreCase))
                        return Node;
            }
            else if (ElemType == "Line")
            {
                foreach (CIM_Element Line in MM_Database_CIM.ElementsOfType("cim:Line"))
                    if (String.Equals(Line.Name, ElemName, StringComparison.CurrentCultureIgnoreCase))
                    {
                        foreach (CIM_Element Seg in Line.InLinks)
                            if (Array.IndexOf(Seg.ACSegmentStations, SubElement) != -1)
                                return Seg;
                    }                     
                    else
                        foreach (CIM_Element Seg in Line.InLinks)
                            if ((Seg.Type == "cim:ACLineSegment" || Seg.Type == "cim:SeriesCompensator") && (Line.Name + Seg.Name).Equals(ElemName, StringComparison.CurrentCultureIgnoreCase))
                                return Seg;



                foreach (CIM_Element Node in SubElement.Elements)
                    if (Node.Type == "cim:ConnectivityNode" && String.Equals(Node.Name, ElemName, StringComparison.CurrentCultureIgnoreCase))
                    {
                        CIM_Element ACLine = Node.Parse("cim:Terminal>cim:ACLineSegment>cim:Line");
                        if (ACLine != null)
                            return ACLine;
                        else if ((ACLine = Node.Parse("cim:Terminal>cim:SeriesCompensator>cim:Line")) != null)
                            return ACLine;
                        else
                            throw new InvalidDataException("Unable to find a line off this node!");
                    }
                    else if (Node.Type == "cim:BusbarSection" && String.Equals(Node.Nodes[0].Name, ElemName, StringComparison.CurrentCultureIgnoreCase))
                    {
                        CIM_Element ACLine = Node.Nodes[0].Parse("cim:Terminal>cim:ACLineSegment>cim:Line");
                        if (ACLine != null)
                            return ACLine;
                        else if ((ACLine = Node.Parse("cim:Terminal>cim:SeriesCompensator>cim:Line")) != null)
                            return ACLine;
                        else
                            throw new InvalidDataException("Unable to find a line off this node!");
                    }
            }
            else if (SubElement == null)
                return null;
            else
            {
                //First, locate our voltage level.
                if (VoltageLevel.Contains("KV") && VoltageLevel[VoltageLevel.IndexOf("KV") - 1] != ' ')
                    VoltageLevel = VoltageLevel.Replace("KV", " KV");
                MM_KVLevel Voltage = null;
                if (!String.IsNullOrEmpty(VoltageLevel) && !MM_Overall_Display.KVLevels.TryGetValue(VoltageLevel, out Voltage))
                    Voltage = MM_Overall_Display.KVLevels["Other KV"];

                if (ElemType == "ConnectivityNode")
                {
                    foreach (CIM_Element Elem in SubElement.Nodes)
                        if (Elem.Type.Split(':')[1] == ElemType && String.Equals(Elem.Name, ElemName, StringComparison.CurrentCultureIgnoreCase) && (Voltage == null || Voltage == MM_Database_CIM.LocateKVLevel(Elem.VoltageLevel)))
                            return Elem;
                }
                else
                    foreach (CIM_Element Elem in SubElement.Elements)
                        if (Elem.Type.EndsWith(ElemType, StringComparison.CurrentCultureIgnoreCase) && String.Equals(Elem.Name, ElemName, StringComparison.CurrentCultureIgnoreCase) && (Voltage == null || Voltage == MM_Database_CIM.LocateKVLevel(Elem.VoltageLevel)))
                            return Elem;

            }
            //If we couldn't find anything, return nothing.
            return null;
        }        
    }
}
